package com.glodon.paas.document.storage.directory;

import static org.apache.commons.io.IOUtils.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.security.MessageDigest;

import org.apache.commons.io.FileUtils;

import com.glodon.paas.document.storage.id.ObjectResult;
import com.glodon.paas.document.storage.util.DigestState;
import com.glodon.paas.document.storage.util.StorageUtil;

public class FileDirectory extends AbstractDirectory {
    private File baseDir;

    public FileDirectory() {
    }

    public FileDirectory(String base) throws IOException {
        baseDir = new File(base);
    }

    @Override
    public ObjectResult save(InputStream is) throws IOException {
        File tempFile = createTempFile();
        FileOutputStream out = new FileOutputStream(tempFile);
        ObjectResult objectResult = StorageUtil.toOut(is, out);
        String objectId = objectResult.getObjectId();
        closeQuietly(out);
        saveById(tempFile, objectId);
        return objectResult;
    }

    private void saveById(File file, String objectId) throws IOException {
        if (has(objectId)) {
            file.delete();
        } else {
            getAudit().addObject(objectId);
            File dest = getFile(objectId);
            FileUtils.forceMkdir(dest.getParentFile());
            file.renameTo(dest);
        }
    }

    private File createTempFile() throws IOException {
        File nozDir = new File(baseDir, NOZ);
        FileUtils.forceMkdir(nozDir);
        return File.createTempFile(NOZ, "", nozDir);
    }

    @Override
    public boolean has(String id) {
        return getFile(id).exists();
    }

    private File getFile(String objectId) {
        return new File(baseDir, getKey(objectId));
    }

    @Override
    public InputStream get(String objectId) throws IOException {
        if (!has(objectId))
            return null;
        FileInputStream fin = getIn(objectId);
        return StorageUtil.getStorageInputStream(fin);
    }

    @Override
    public InputStream get(String id, long start) throws IOException {
        if (!has(id))
            return null;
        RandomAccessFile ra = new RandomAccessFile(getFile(id), "r");
        ra.seek(start);
        InputStream in = Channels.newInputStream(ra.getChannel());
        return StorageUtil.getStorageInputStream(in);
    }

    private FileInputStream getIn(String id) throws FileNotFoundException {
        File file = getFile(id);
        return new FileInputStream(file);
    }

    public void setBaseDir(File baseDir) {
        this.baseDir = baseDir;
    }

    @Override
    public String initPart() throws IOException {
        String partId = StorageUtil.generateUUID();
        File partFile = getPartFile(partId);
        partFile.createNewFile();
        return partId;
    }

    private File getPartFile(String id) throws IOException {
        File partFile = new File(baseDir, getPartPath(id));
        FileUtils.forceMkdir(partFile.getParentFile());
        return partFile;
    }

    @Override
    public ObjectResult append(String id, int partNumber, String state, InputStream is, long length) throws IOException {
        File partFile = getPartFile(id);
        if (!partFile.exists())
            throw new IOException("part file is not exists");
        FileOutputStream out = new FileOutputStream(partFile, true);
        DigestState digestState;
        if (state != null) {
            digestState = DigestState.getDigestState(state);
        } else {
            digestState = new DigestState();
        }
        MessageDigest md = digestState.getMessageDigest();
        ObjectResult objectResult = StorageUtil.toOut(is, out, md);
        closeQuietly(out);
        long l = objectResult.getLength();
        if (l < length) {
            partFile.delete();
            throw new IOException("append file error, expect length: " + length + ", actual: " + l);
        }
        objectResult.setLength(l);
        return objectResult;
    }

    @Override
    public void complete(String id, String objectId) throws IOException {
        File partFile = getPartFile(id);
        saveById(partFile, objectId);
    }

    @Override
    public void abortPart(String partId) throws IOException {
        File partFile = getPartFile(partId);
        if (partFile.exists())
            partFile.delete();
    }
}
